package com.zhz.common.feign;

import feign.Request;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * FeignOptionsInterceptor
 * <p>
 * 通过 Options 注解控制方法级别的超时时间
 *
 * @author zhouhengzhe
 */
@Aspect
@Component
public class FeignOptionsInterceptor {

    static final ThreadLocal<Request.Options> OPTIONS_HOLDER = new ThreadLocal<>();


    @Pointcut("@annotation(Options)")
    public void optionsPointcut() {}


    @Pointcut("execution(* feign.Client.execute(..))")
    public void feignClientPointcut() {}


    @Around("optionsPointcut() && @annotation(options)")
    public Object aroundFeignApi(ProceedingJoinPoint joinPoint, Options options) throws Throwable {
        // 如果入参中含有 Options 选项, 则不进行对 Options 注解的处理
        if (OPTIONS_HOLDER.get() == null && !isOptionsParamExists(joinPoint.getArgs())) {
            OPTIONS_HOLDER.set(toOptions(options));
        }

        try {
            return joinPoint.proceed();
        } finally {
            removeOptions();
        }
    }


    @Around("feignClientPointcut()")
    public Object aroundFeignClientExecute(ProceedingJoinPoint joinPoint) throws Throwable {
        Request.Options options = OPTIONS_HOLDER.get();
        if (options == null) {
            return joinPoint.proceed();
        }

        try {
            Object[] args = joinPoint.getArgs();
            ((MethodSignature) joinPoint.getSignature()).getParameterTypes()[args.length - 1].asSubclass(Request.Options.class);

            // 替换掉默认的超时参数
            args[args.length - 1] = options;
            return joinPoint.proceed(args);
        } finally {
            removeOptions();
        }
    }


    /**
     * 判断 feign api 的参数中是否存在 Options 参数
     */
    private boolean isOptionsParamExists(Object[] args) {
        if (args == null) {
            return false;
        }
        for (Object arg : args) {
            if (arg instanceof Request.Options) {
                return true;
            }
        }
        return false;
    }


    private Request.Options toOptions(Options options) {
        return new Request.Options(options.connectTimeout(), TimeUnit.SECONDS,
                options.readTimeout(), TimeUnit.SECONDS, options.followRedirects());
    }


    private void removeOptions() {
        OPTIONS_HOLDER.remove();
    }


}
